//#include "stdafx.h"
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <iomanip>
#include <fstream>

#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/device/file.hpp>

#include "myhttp.h"
#include "functions_th.h"

namespace tool_th{
myhttp::myhttp()
    :ishttps_(false),ios_(),
	ctx_(boost::asio::ssl::context::tlsv12_client),ssl_socket_(NULL),
	isfinished_(false)
{
	
}
myhttp::~myhttp()
{
	clear();
}
void myhttp::clear()
{
	user_.clear();
	pass_.clear();
	host_.clear();
	port_.clear();
	request_headers_.clear();
	field_ = "/";
	ishttps_ = false;

	if(ssl_socket_)
	{
		boost::system::error_code ec;
		ssl_socket_->shutdown(ec);
		ssl_socket_->next_layer().close(ec);
		delete ssl_socket_;
		ssl_socket_ = NULL;
	}

	response_code_.clear();
	response_headers_.clear();

	response_text_.clear();
	response_temp_.clear();
	response_text_decoded_.clear();

	chunked_index_ = 0;

	is_all_received_ = false;
	isfinished_ = false;

}
void myhttp::init(const std::string &host,const std::string &port,const std::string field,const bool &ishttps,
				  const std::map<std::string,std::string> &request_header)
{
	clear();

	host_ = host;
	port_ = port;
	field_ = field;
	ishttps_ = ishttps;
	request_headers_ = request_header;

	if (!ssl_socket_)
		ssl_socket_ = new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(ios_,ctx_);

}
void myhttp::init(const std::string &url,const std::map<std::string,std::string> &request_header)
{
	clear();

	parseURL(url,user_,pass_,host_,port_,field_,ishttps_);
	request_headers_ = request_header;

	if (!ssl_socket_)
		ssl_socket_ = new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(ios_,ctx_);
}
bool myhttp::gethtml()
{
	bool result = true;
	try
	{
		boost::asio::ip::tcp::resolver myresolver(ios_);
		boost::asio::ip::tcp::resolver::query myquery(host_,port_);
		boost::asio::ip::tcp::resolver::iterator iter = myresolver.resolve(myquery);

		ssl_socket_->next_layer().connect(*iter);
		if(ishttps_)
			ssl_socket_->handshake(boost::asio::ssl::stream_base::client);

		boost::asio::streambuf sendbuf;
		
		std::ostream outs(&sendbuf);
		outs <<"GET "<<field_<<" HTTP/1.1\r\n";
		for(auto iter = request_headers_.begin(); iter!= request_headers_.end();++iter)
			outs <<iter->first<<":"<<iter->second<<"\r\n";
		outs <<"\r\n";

		if(ishttps_)
			boost::asio::write(*ssl_socket_,sendbuf);
		else
			boost::asio::write(ssl_socket_->next_layer(),sendbuf);


		//get response header
		if(!read_response_header())
			return false;

		//get content
		while (!is_all_received_ && !isfinished_)
			read_content();

		//decompression content
		if(!isfinished_)
			decompression_data();
	}
	catch (const std::exception &e)
	{
		print_msg(e.what());
		result = false;
	}
	catch(...)
	{
		print_msg("Unknown Exception");
		result = false;
	}
	
	boost::system::error_code ec;
	ssl_socket_->shutdown(ec);
	ssl_socket_->next_layer().close(ec);

	return result;
}
std::string myhttp::response_code()
{
	if(response_code_.size() >=2)
		return response_code_[1];
	else
		return "000";
}
std::string myhttp::response_text_decoded_before()
{
	return response_text_;
}
std::string myhttp::response_text_decoded()
{
	return response_text_decoded_;
}
void myhttp::printReuslt(bool output_to_screen,const std::string &decode_before_file_name,const std::string &decoded_file_name)
{
	print_msg("response code:");
	for(auto ch :response_code_)
		print_msg(" "+ch,false);
	print_msg();

	print_msg("response headers:");
	for(auto item :response_headers_)
		print_msg(item.first+": "+item.second);
	print_msg();

	print_msg("response text decoded before: "+std::to_string(response_text_.size()));
	hexdump((const unsigned char *)response_text_.c_str(),response_text_.size(),output_to_screen,decode_before_file_name);
	print_msg();

	print_msg("response text decoded: "+std::to_string(response_text_decoded_.size()));
	hexdump((const unsigned char *)response_text_decoded_.c_str(),response_text_decoded_.size(),output_to_screen,decoded_file_name);
}
std::string myhttp::ungzip(const std::string &str)
{
	std::istringstream ss_ins(str);
	std::ostringstream ss_decomp;

	boost::iostreams::filtering_istream in_filter;
	in_filter.push(boost::iostreams::gzip_decompressor());
	in_filter.push(ss_ins);//from stringstream
	//in_filter.push(boost::iostreams::file_source("test.txt"));		//from file
	boost::iostreams::copy(in_filter, ss_decomp);

	return ss_decomp.str();
}
std::string myhttp::gzip(const std::string &str)
{
	std::istringstream ss_ins(str);
	std::ostringstream ss_comp;

	boost::iostreams::filtering_ostream out;
	out.push(boost::iostreams::zlib_compressor());
	out.push(ss_comp);	//to stringstream
	//out.push(boost::iostreams::file_sink("test.txt"));		//to file
	boost::iostreams::copy(ss_ins, out);

	return ss_comp.str();
}
inline void myhttp::print_msg(const std::string &str,bool need_newline)
{
	if(need_newline)
		std::cout<<str<<std::endl;
	else
		std::cout<<str;
}
bool myhttp::read_response_header()
{
	bool result = true;

	if(ishttps_)
		boost::asio::read_until(*ssl_socket_,recvbuf_,"\r\n\r\n");
	else
		boost::asio::read_until(ssl_socket_->next_layer(),recvbuf_,"\r\n\r\n");


	if(recvbuf_.size() ==0 )
	{
		result = false;
		return result;
	}

	std::istream ins(&recvbuf_);
	std::string header_field;

	//get response code
	std::getline(ins,header_field);
	//print_msg(header_field);
	response_code_ = splitBy(header_field.substr(0,header_field.size()-1)," ");

	//get headers
	std::vector<std::string> tmp_str;
	while(std::getline(ins,header_field) && header_field != "\r")
	{
		tmp_str = splitBy(header_field,": ");
		if (tmp_str.size() >= 2)
		{
			tool_th::lower(tmp_str[0]);
			tool_th::lower(tmp_str[1]);
			response_headers_[tmp_str[0]] = tmp_str[1].substr(0,tmp_str[1].size()-1);
		}
		else
			break;
	}
	return result;
}
void myhttp::read_content()
{
	//read data 0
	std::string temp_str;
	if(recvbuf_.size())
	{
		std::istream ins(&recvbuf_);
		std::size_t size_buf = recvbuf_.size();
		std::size_t size_bef = response_text_.size();

		while(std::getline(ins,temp_str))
		{
			temp_str+='\n';
			response_text_ +=temp_str;
		}
		if(response_text_.size() - size_bef > size_buf)
			response_text_.erase(response_text_.size()-1,1);
	}

	// read data 1
 	if (response_headers_.find("content-length")!= response_headers_.end())
	{
		long length = std::atol(response_headers_["content-length"].c_str());
		
		if (length  && length <= response_text_.size() )
		{
			response_temp_ = response_text_.substr(0,length);
			is_all_received_ = true;
		}
		else if(!length)
			isfinished_ = true;
		else
			continue_to_read();
	}
	else if (response_headers_.find("transfer-encoding")!= response_headers_.end())
	{
		if(response_headers_["transfer-encoding"] =="chunked")
		{
			std::string::size_type nend;
			while ((nend = response_text_.find("\r\n", chunked_index_)) != std::string::npos)
			{
				std::string subchunked;

				subchunked = response_text_.substr(chunked_index_, nend-chunked_index_);

				long length = std::strtol(subchunked.c_str(), (char**)NULL, 16);

				//是否得到一个完整的chunked ,即"length \r\n data \r\n "
				if(nend + 2 + length + 2<= response_text_.size())//将chunked后面的\r\n也算上
				{
					if(!length)//最后一个chunked
					{
						is_all_received_ = true;
						break;
					}

					chunked_index_ = nend + 2;
					subchunked = response_text_.substr(chunked_index_, length);
					response_temp_ +=subchunked;

					chunked_index_ += ( length+ 2);

				}
				else
					break;
			}
			if(!is_all_received_)
				continue_to_read();
		}
		else
		{
			print_msg("unknown transfer-encoding: "+response_headers_["transfer-encoding"]);
			isfinished_ = true;
		}
	}
	else
	{
		print_msg("response headers has neighter content-length nor transfer-encoding");
		isfinished_ = true;
	}
}
void myhttp::continue_to_read()
{
	if(ishttps_)
		boost::asio::read_until(*ssl_socket_,recvbuf_,"\r\n");
	else
		boost::asio::read_until(ssl_socket_->next_layer(),recvbuf_,"\r\n");
}
void myhttp::decompression_data()
{
	//decompression data
	if( is_all_received_ && !isfinished_ &&response_temp_.size())
	{
		if(response_headers_.find("content-encoding")!=response_headers_.end())
		{
			if(response_headers_["content-encoding"] =="gzip")
				response_text_decoded_ = ungzip(response_temp_);

			else
				print_msg("unknown zip :" + response_headers_["content-encoding"] );
		}
		else
			response_text_decoded_ = response_temp_;
	}

	isfinished_ = true;
}
void myhttp::hexdump(const unsigned char *pdata,int len,bool output_to_screen,const std::string file_name)
{
	std::stringbuf obuf;
	std::ostream os(&obuf); 

	int j;
	char c;
	unsigned int offset = 0;

	int total_row = len / 16;
	int left_data_len = len % 16;

	for (int i=0; i<total_row; i++) 
	{
		os << "#" << std::setfill('0') << std::setw(8) << std::hex << offset; 
		os << "    "; 
		for (j=0; j<16; j++)
		{
			os << std::setfill('0') << std::setw(2) << std::hex << (const unsigned short)pdata[i*16+j] << " ";
		}
		os << "   ";
		for (j=0; j<16; j++)
		{
			c=pdata[i*16+j]; 
			if (0x20 <= (unsigned char)c && (unsigned char)c <= 0x7f)
			{
				os << c;
			}
			else
			{
				os << ".";
			}
		}
		offset += 16;
		os << "\n";
	}

	if (left_data_len>0)
	{
		os << "#" << std::setfill('0') << std::setw(8) << std::hex << offset;
		os << "    ";
		for (j=0; j<left_data_len; j++)
		{
			os << std::setfill('0') << std::setw(2) << std::hex << (const unsigned short)pdata[total_row*16+j] << " ";
		}

		for (j=0; j<16-left_data_len; j++) 
		{
			os << "   "; 
		}
		os << "   "; 
		for (j=0; j<left_data_len; j++)
		{
			c=pdata[total_row*16+j];  
			if (0x20 <= (unsigned char)c && (unsigned char)c <= 0x7f)
			{
				os << c;
			}
			else
			{
				os << "."; 
			}
		}
		os << "\n";  
	}

	//output
	if(output_to_screen)
		print_msg(obuf.str());
	else
	{
		if(file_name.empty())
			return;

		std::ofstream ff(file_name);
		if(ff)
		{
			std::string aba(obuf.str());
			ff << aba;
			ff.flush();
			ff.close();
		}
	}
	
}
}